/**
 * @author W
 * @date 2020/8/31 22:08
 */
(function () {
    class wLazyImg {
        constructor(options) {
            this.options = Object.assign({
                threshold: [0.5],  // 控制在什么阶段执行图片的加载：0 图片盒子刚刚出现在可视窗口中   0.5 图片盒子的一半出现   1 图片盒子已经全部出现
                attr: 'data-image', // img 存放真实图片路径的属性
                loaded: () => {}, // 每一张图片加载后的回调
                animate: true //  是否启用图片加载成功时的过渡动画
            }, options)

            // 获取所有具有指定属性的图片（需要懒加载的图片的标识）
            this.imgBoxs = Array.from(document.querySelectorAll(`img[${this.options.attr}]`));

            // 检测浏览器是否支持IntersectionObserver方法
            const observerFunString = 'function IntersectionObserver() { [native code] }';
            this.isSupportObserve = typeof IntersectionObserver === 'function' && Function.toString.call(IntersectionObserver) === observerFunString;

            // 采用不同方案实现，效果一样
            if (this.isSupportObserve) {
                this.watch();
            } else {
                // 初始化需要加载一次
                this.scrollLoad();
                // 后续的使用滚动加载
                window.addEventListener('scroll', this.scrollLoad.bind(this))
            }
        }

        // 使用IntersectionObserver API 方式实现图片懒加载
        watch() {
            const {threshold, attr, animate} = this.options;

            const observer = new IntersectionObserver(changes => {
                changes.forEach(item => {
                    const {target, isIntersecting} = item;
                    const img = target.querySelector(`img[${attr}]`);
                    const trueSrc = img && img.getAttribute(attr);
                    if (isIntersecting && trueSrc) { //  到达指定的条件以及具有真实图片路径的就进行懒加载
                        this.loadImg(img, trueSrc, observer)
                    }
                })
            }, {
                threshold: threshold  // 指定触发回调的门槛
            })

            // 遍历每个盒子，监听其父元素，如果开启动画，则设置相应的初始化状态
            this.imgBoxs.forEach(img => {
                if (animate) {
                    img.style.opacity = 0;
                    img.style.transition = 'opacity 500ms linear';
                }
                observer.observe(img.parentNode)
            })

        }

        // 使用getBoundingClientRect() 方式实现
        scrollLoad() {
            // 获取可见视口的高度
            const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
            const {threshold, attr, animate} = this.options;
            this.imgBoxs.forEach(img => {
                if (animate && img.style.opacity === '') {
                    img.style.opacity = 0;
                    img.style.transition = 'opacity 500ms linear';
                }
                // 获取图片盒子最底部边框距离屏幕顶部的大小
                const imgBoxBottomOffsetBody = img.parentNode.getBoundingClientRect().bottom;

                // 获取图片盒子最顶部边框距离屏幕顶部的大小
                const imgBoxTopOffsetBody = img.parentNode.getBoundingClientRect().top;

                // 获取图片盒子的高度
                const imgBoxHeight = parseFloat(getComputedStyle(img.parentNode).height);

                // 获取图片盒子的一半高度的位置距离屏幕顶部的大小
                const halfImgBoxOffsetBody = imgBoxBottomOffsetBody - imgBoxHeight / 2;

                const trueSrc = img.getAttribute(attr);
                if (!trueSrc) return;

                // 图片盒子刚刚出现在视口中就加载：图片盒子最顶部边框距离屏幕顶部的大小要小于等于视口的高度
                // 同时为了保证只在当前一屏里的图片才加载，不在的就不加载，所以加上额外的条件：获取图片盒子最底部边框距离屏幕顶部的大小要大于等于0
                // 这样就能保证只在当前一屏中滚动条向下滚动刚露头的、向上滚动刚露尾的才加载
                if (threshold[0] === 0 && imgBoxTopOffsetBody <= clientHeight && imgBoxBottomOffsetBody >= 0) {
                    this.loadImg(img, trueSrc);
                    return;
                }
                // 图片盒子的一半出现在视口时就加载：图片盒子的一半高度的位置距离屏幕顶部的大小应该小于等于视口的高度
                // 同时图片盒子的一半高度的位置距离屏幕最顶部的大小要大于等于0，这样就能保证只在当前一屏中滚动条向下滚动到盒子一半范围的、向上滚动到盒子一半范围的才加载
                if (threshold[0] === 0.5 && halfImgBoxOffsetBody <= clientHeight && halfImgBoxOffsetBody >= 0) {
                    this.loadImg(img, trueSrc);
                    return;
                }
                // 图片盒子的全部出现在视口时就加载：图片盒子最底部边框距离屏幕顶部的大小要小于等于视口的高度
                // 同时图片盒子最顶部边框距离屏幕顶部的大小要大于等于0，这样就能保证只在当前一屏中滚动条向下滚动到盒子全部范围的、向上滚动到盒子全部范围的才加载
                if (threshold[0] === 1 && imgBoxBottomOffsetBody <= clientHeight && imgBoxTopOffsetBody >= 0) {
                    this.loadImg(img, trueSrc);
                }
            })
        }

        /**
         * 加载真实图片地址
         * @param img  图片元素
         * @param imgSrc 真实地址
         * @param observer IntersectionObserver的实例
         */
        loadImg(img, imgSrc, observer) {
            const {animate, loaded, attr} = this.options;
            img.src = imgSrc;
            img.onload = (event) => {
                if (animate) setTimeout(() => img.style.opacity = 1);
                img.removeAttribute(attr);
                // 有监听器表示是IntersectionObserver的实例，加载后取消对元素的监听。
                observer && observer.unobserve(img.parentNode);
                // 每一张图片成功加载后的回调
                loaded.call(this, event)
            }
        }

        // 往外暴露一个静态方法，返回一个懒加载类的实例。
        static init(options = {}) {
            return new wLazyImg(options)
        }
    }

    if (typeof window !== 'undefined') {
        window.wLazyImg = wLazyImg
    }
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = wLazyImg;
    }
})();

// 使用方式
// wLazyImg.init({
//     threshold:[0.5],
//     attr:'data-image',
//     loaded:()=>{},
//     animate:true
// });
